using System.Collections.Generic;
using Kopernikus.Collections.Generic;
using Kopernikus.Parsing.LR;
using Kopernikus.Parsing.Utility;

namespace Kopernikus.Parsing.Earley
{
    public class FullEarleyDFA : EarleyDFA
    {
        public FullEarleyDFA(IGrammar grammar)
            : base(grammar)
        {
        }

        public override int Build()
        {
            int initialState = base.Build();


            WorkList<Pair<int, Symbol>> workList = new WorkList<Pair<int, Symbol>>(false);
            AddGotoToWorkList(workList, true, initialState);

            Pair<int, Symbol> workItem;
            while (workList.Dequeue(out workItem))
            {
                int state = workItem.First;
                Symbol symbol = workItem.Second;
                Set<LR0Item> stateSet = StateSets[state];

                bool isEpsilonGoto = symbol.Equals(Symbol.Epsilon);

                Set<LR0Item> gotoSet;
                if (isEpsilonGoto)
                {
                    gotoSet = GetClosure(stateSet);
                    gotoSet.Remove(stateSet);
                }
                else
                {
                    gotoSet = AdvanceStateItems(symbol, stateSet);
                }
                int gotoState = AddStateSet(gotoSet);
                DefineGoto(state, symbol, gotoState);

                AddGotoToWorkList(workList, !isEpsilonGoto, gotoState);
            }
            return initialState;
        }

        private void AddGotoToWorkList(WorkList<Pair<int, Symbol>> workList, bool enqueueEpsilonGoto, int state)
        {
            if (state == InvalidState)
            {
                return;
            }

            if (enqueueEpsilonGoto)
            {
                workList.Enqueue(new Pair<int, Symbol>(state, Symbol.Epsilon));
            }
            foreach (Symbol gotoSymbol in GetDotSymbols(StateSets[state]))
            {
                workList.Enqueue(new Pair<int, Symbol>(state, gotoSymbol));
            }
        }

        private static Set<Symbol> GetDotSymbols(IEnumerable<LR0Item> items)
        {
            Set<Symbol> symbols = new Set<Symbol>();
            foreach (LR0Item item in items)
            {
                if (!item.IsComplete)
                {
                    symbols.Add(item.DotSymbol);
                }
            }
            return symbols;
        }
    }
}